package com.szy.assembly;

import java.io.Serializable;
import java.util.ArrayList;

public class CPUSimulator implements Serializable,InterruptRequest{
    private Program program;
    private boolean isRunning=false;
    private int ramSize;
    private int pc;
    private int stack_top=0x1f;

    private ArrayList<Peripheral> peripherals;

    public Ram ram;

    public CPUSimulator(Program program, int ramSize, ArrayList<Peripheral> peripherals){
        this.program=program;
        this.ramSize=ramSize;
        this.peripherals=peripherals;
    }

    public void setProgram(Program program){
        this.program=program;
    }
    public ArrayList<Peripheral> getPeripherals(){
        return peripherals;
    }
    public Instruction getCurrentInstruction(){
        return program.getInstruction(pc);
    }
    public void restartPeriodically(){
        pc=0;
        stack_top=0x1f;
        //ram=new HashMap<>();
        ram=new Ram(ramSize,peripherals);
        isRunning=true;
    }
    public void pause(){
        isRunning=false;
    }
    public void resume(){
        isRunning=true;
    }

    public boolean runningState(){
        return isRunning;
    }

    public void cycleTask(){
        if(isRunning){
            Instruction ins=program.getInstruction(pc);
            pc++;
            executeInstruction(ins);

            if(lastInterruptName != null){
                ram.set(stack_top,pc);
                stack_top--;
                pc=InterruptVector.entranceAddress.get(lastInterruptName);
                lastInterruptName = null;
            }
            for(Peripheral peripheral:peripherals){
                peripheral.tickTask();
            }
        }
    }


    private void executeInstruction(Instruction ins){
        switch (ins.opcode){
            case IMM:
                ram.set(ins.operand[0],ins.operand[1]);
                break;
            case MOV:
                ram.set(ins.operand[0], ram.get(ins.operand[1]));
                break;
            case LD:
                ram.set(ins.operand[0], ram.get(ram.get(ins.operand[1])));
                break;
            case ST:
                ram.set(ram.get(ins.operand[0]), ram.get(ins.operand[1]));
                break;
            case ADD:
                ram.set(ins.operand[0],ram.get(ins.operand[1])+ram.get(ins.operand[2]));
                break;
            case SUB:
                ram.set(ins.operand[0],ram.get(ins.operand[1])-ram.get(ins.operand[2]));
                break;
            case INC:
                ram.set(ins.operand[0],ram.get(ins.operand[0])+1);
                break;
            case DEC:
                ram.set(ins.operand[0],ram.get(ins.operand[0])-1);
                break;
            case MUL:
                ram.set(ins.operand[0],ram.get(ins.operand[1])*ram.get(ins.operand[2]));
                break;
            case DIV:
                ram.set(ins.operand[0],ram.get(ins.operand[2])/ram.get(ins.operand[3]));
                ram.set(ins.operand[1],ram.get(ins.operand[2])%ram.get(ins.operand[3]));
                break;
            case AND:
                ram.set(ins.operand[0],ram.get(ins.operand[1]) & ram.get(ins.operand[2]));
                break;
            case NOT:
                ram.set(ins.operand[0],~ram.get(ins.operand[1]));
                break;
            case OR:
                ram.set(ins.operand[0],ram.get(ins.operand[1]) | ram.get(ins.operand[2]));
                break;
            case XOR:
                ram.set(ins.operand[0],ram.get(ins.operand[1]) ^ ram.get(ins.operand[2]));
                break;
            case LTH:
                ram.set(ins.operand[0],ram.get(ins.operand[1]) < ram.get(ins.operand[2])?1:0);
                break;
            case MTH:
                ram.set(ins.operand[0],ram.get(ins.operand[1]) > ram.get(ins.operand[2])?1:0);
                break;
            case EQ:
                ram.set(ins.operand[0],ram.get(ins.operand[1]) == ram.get(ins.operand[2])?1:0);
                break;
            case J:
                pc=ins.operand[0];
                break;
            case JE:
                if(ram.get(ins.operand[1]) == ram.get(ins.operand[2])){
                    pc=ins.operand[0];
                }
                break;
            case JNE:
                if(ram.get(ins.operand[1]) != ram.get(ins.operand[2])){
                    pc=ins.operand[0];
                }
                break;
            case JZ:
                if(ram.get(ins.operand[1]) == 0){
                    pc=ins.operand[0];
                }
                break;
            case JNZ:
                if(ram.get(ins.operand[1]) != 0){
                    pc=ins.operand[0];
                }
                break;
            case DJZ:
                int n1=ram.get(ins.operand[1])-1;
                if(n1 == 0){
                    pc=ins.operand[0];
                }
                ram.set(ins.operand[1],n1);
                break;
            case DJNZ:
                int n2=ram.get(ins.operand[1])-1;
                if(n2 != 0){
                    pc=ins.operand[0];
                }
                ram.set(ins.operand[1],n2);
                break;
            case NOP:
                break;
            case CALL:
                ram.set(stack_top,pc);
                stack_top--;
                if(stack_top < 0){
                    //cause stack overflow interrupt
                }
                pc=ins.operand[0];
                break;
            case RET:
                stack_top++;
                pc=ram.get(stack_top);
                break;
        }
    }

    public InterruptVector.InterruptName lastInterruptName=null;
    @Override
    public void interruptHandle(InterruptVector.InterruptName name) {
        lastInterruptName=name;
    }
}
